﻿using System;
using System.Collections.Generic;
using RbmlVisualizer.Data;
using RbmlVisualizer.Data.Blocks;
using RbmlVisualizer.Data.Link;
using RbmlVisualizer.Utilities;

namespace RbmlVisualizer.DiagramComparer
{
    /// <summary>
    /// This class is responsible for converting the IEntities connected by an ILink to an IBlock.
    /// </summary>
    public class DiagramConverter
    {
        private DiagramConverter()
        {
        }

        //TODO I should maybe just convert this to a static class if we're not going to use a dependency injection engine.
        public static DiagramConverter Instance
        {
            get
            {
                return _instance ?? (_instance = new DiagramConverter());
            }
        }
        private static DiagramConverter _instance;

        /// <summary>
        /// This is the main method that converts an IClassDiagram into a list of blocks
        /// </summary>
        /// <param name="classDiagram">The diagram to be converted (can be either UML or RBML)</param>
        /// <returns>A list of converted blocks</returns>
        public List<ClassifierBlock> ConvertToClassifierBlocks(IClassDiagram classDiagram)
        {
            var roleBlocks = new List<ClassifierBlock>();

            foreach (var link in classDiagram.Links)
            {
                var block = new ClassifierBlock
                {
                    Classifier1 = CreateClassifierForEntity(link.EntityOne, classDiagram.ClassDiagramType),
                    Classifier2 = CreateClassifierForEntity(link.EntityTwo, classDiagram.ClassDiagramType),
                    Relationship = CreateRelationshipForLink(link, classDiagram.ClassDiagramType),
                    ParentDiagramType = classDiagram.ClassDiagramType
                };
                roleBlocks.Add(block);
            }

            return roleBlocks;
        }

        /// <summary>
        /// Converts the ILink into an appropriate relationship.
        /// </summary>
        /// <param name="link">The link to convert</param>
        /// <param name="diagramType">The type of diagram</param>
        /// <returns>A relationship.</returns>
        private static BlockLink CreateRelationshipForLink(ILink link, ClassDiagramType diagramType)
        {
            var endDecorators = BlockTypeConverter.ConvertLinkToBlockLinkDecorators(link, diagramType);
            var blockType = BlockTypeConverter.ConvertLinkToBlockType(link, diagramType);
            return new BlockLink
            {
                BlockType = blockType,
                End1 = endDecorators.Item1,
                End2 = endDecorators.Item2,
                //TODO needs to be different for UML.
                Name = NameUtilities.GetNameFromUid(link.Label)
            };
        }

        /// <summary>
        /// Converts the IEntity to a Classifier.
        /// </summary>
        /// <param name="entity">The entity to convert.</param>
        /// <param name="diagramType">Diagram type</param>
        /// <returns>The converted Classifier</returns>
        private static Classifier CreateClassifierForEntity(IEntity entity, ClassDiagramType diagramType)
        {
            var classifier = new Classifier();

            foreach (var fieldOrMethod in entity.EntityParts)
            {
                //if the entity part contains a ")" it is assumed to be a method
                if (fieldOrMethod.Value.Contains(")"))
                    classifier.BehaviorRoles.Add(CreateBehaviorRoleFromMethod(fieldOrMethod));
                else
                    classifier.AttributeRoles.Add(CreateAttributeRoleFromField(fieldOrMethod));
            }
            classifier.Multiplicity = MultiplicityConverter.ConvertFromString(entity.Uid);

            classifier.Name = NameUtilities.GetNameFromUid(entity.Uid);
            
            if (diagramType == ClassDiagramType.Rbml)
                classifier.IsConcrete = NameUtilities.IsRoleClassConcrete(entity.Uid);
            else
                classifier.IsConcrete = entity.EntityType != EntityType.Abstract && entity.EntityType != EntityType.Interface;

            return classifier;
        }

        /// <summary>
        /// Converts a method to a behavior.
        /// </summary>
        /// <param name="method">The method, which should be in the form MethodName(Type1:param) 1.*</param>
        /// <returns>A behavior</returns>
        private static ClassifierBehavior CreateBehaviorRoleFromMethod(IEntityPart method)
        {
            var behaviorRole = new ClassifierBehavior();
            //Look after the ) and see if there is a multiplicity
            var multiplictySplit = method.Value.Split(')');
            if (multiplictySplit.Length == 2) //we have a multiplicity
                behaviorRole.Multiplicity = MultiplicityConverter.ConvertFromString(multiplictySplit[1]);

            //Now split the string into a name and a list of params, which should be colon seperated.
            var methodString = multiplictySplit[0].Split(new[] { '(', ':' }, StringSplitOptions.RemoveEmptyEntries);

            var i = 0;
            while (i < methodString.Length)
            {
                if (i == 0)
                    behaviorRole.Name = methodString[i];
                else
                {
                    //roles should always appear in 2s.  If need be we can make this more safe.
                    behaviorRole.Paramaters.Add(new Tuple<string, string>(methodString[i], methodString[i + 1]));
                    i++;
                }

                i++;
            }

            return behaviorRole;
        }

        /// <summary>
        /// Converts the field to an attribute.
        /// </summary>
        /// <param name="field">The field, which should be in the form Type:Name Multiplicty</param>
        /// <returns></returns>
        private static ClassifierAttribute CreateAttributeRoleFromField(IEntityPart field)
        {
            var attributeRole = new ClassifierAttribute();
            var fieldString = field.Value.Split(':');
            if (fieldString.Length != 2)
                throw new InvalidOperationException("There should be only 2, but we found:" + field.Value);

            //in the case where we have multiplicity, set it.
            var typeAndMultiplicityString = fieldString[1].Trim().Split(' ');
            if (typeAndMultiplicityString.Length == 2)
                attributeRole.Multiplicity = MultiplicityConverter.ConvertFromString(typeAndMultiplicityString[1]);

            attributeRole.AttributeType = typeAndMultiplicityString[0];
            attributeRole.Name = fieldString[0];

            return attributeRole;
        }
    }
}